/*
 * Author: jeremiazhao (赵振)
 *
 * This file is part of libldevice
 *
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
 * more details.
 *
 * You should have received a copy of the GNU Lesser General Public  License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
// for arch
#include <sys/utsname.h>
// for network device
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <dirent.h>
#include <errno.h>
#include <netinet/in.h>
#include <net/if.h>
// for disk and filesystem
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <libudev.h>
// for token server
#include <openssl/sha.h>
#include <curl/curl.h>

#include "ldevice.h"


int system_get_tag() {
  int tag = 0;
  char* distro_id = "unkown_opencloudos_unkown";
  FILE *file = fopen("/etc/os-release", "r");
  if (file) {
    char line[1024];
    while (fgets(line, sizeof(line), file)) {
      if (strstr(line, "ID=") && strncmp(strstr(line, "ID="), "ID=", 3) == 0) {
        char *start = strchr(line, '=');
        if (start && *(start + 1) == '"') {
          start = start + 2;
          char *end = strchr(start, '"');
          if (end) {
            *end = '\0';
          }
          distro_id = strdup(start);
          break;
        }
      }
    }
    fclose(file);
  }

  if (strcmp(distro_id, "tencentos") == 0 || strcmp(distro_id, "opencloudos") == 0) {
    tag = 4;
  }
  else {
    tag = 0;
  }

  if (strcmp(distro_id, "unkown_opencloudos_unkown") != 0) {
    free(distro_id);
    distro_id = NULL;
  }
  return tag;
}

// 获取系统名字以及架构
const char* system_get_name() {
  // 获取操作系统名字
  char distro_name[512];
  static char distro_name_and_arch[1024];
  FILE *fp = fopen("/etc/os-release", "r");
  // 获取架构
  struct utsname buffer;
  if(uname(&buffer) != 0)
  {
      perror("uname");
      return NULL;
  }

  if(fp == NULL) {
    printf("Failed to open /etc/os-release\n");
    return NULL;
  }

  while(fgets(distro_name, 255, fp)) {
    if(strncmp(distro_name, "NAME=", 5) == 0) {
      fclose(fp);

      // Remove "NAME=" part along with the quotes
      // 删除可能出现的换行符
      if ( distro_name[strlen(distro_name) - 1] == '\n' ) {
        distro_name[strlen(distro_name) - 1] = '\0';
      }
      memmove(distro_name, distro_name + 6, strlen(distro_name) - 7);
      distro_name[strlen(distro_name) - 7] = '\0';

      sprintf(distro_name_and_arch, "%s (%s)", distro_name, buffer.machine);
      return distro_name_and_arch;
    }
  }

  fclose(fp);
  return NULL;
}

int system_is_active() {
  int is_active = 1;
  return is_active;
}

const char* system_get_osversion() {
  static char distro_version[255]; 
  FILE *fp = fopen("/etc/os-release", "r");

  if(fp == NULL) {
    printf("Failed to open /etc/os-release\n");
    return NULL;
  }

  while(fgets(distro_version, 255, fp)) {
    if(strncmp(distro_version, "VERSION_ID=", 11) == 0) {
      fclose(fp);

      // Remove "NAME=" part along with the quotes
      // 删除可能出现的换行符
      if ( distro_version[strlen(distro_version) - 1] == '\n' ) {
        distro_version[strlen(distro_version) - 1] = '\0';
      }
      memmove(distro_version, distro_version + 12, strlen(distro_version) - 13);
      distro_version[strlen(distro_version) - 13] = '\0';
      
      return distro_version;
    }
  }

  fclose(fp);
  return NULL;
}

/*
 * 需要以下文件，对普通用户可读，原权限为400，改为444：
 * /sys/class/dmi/id/product_uuid
 * /sys/class/dmi/id/product_serial
*/
const char* system_get_uuid() {
  const char *dmi_files[] = {
      "/sys/class/dmi/id/product_uuid",
      "/sys/class/dmi/id/product_serial",
      NULL
  };
  static char dmi_uuid[256];

  for (int i = 0; dmi_files[i] != NULL; ++i) {
    FILE *fp = fopen(dmi_files[i], "r");

    // 如果一个文件不存在，则继续遍历其他文件
    if(fp == NULL) {
      continue;
    }

    // 只读取第一行
    fscanf(fp, "%[^\n]", dmi_uuid);
    fclose(fp);

    //如果文本为空，且只有一个换行符
    if ( strlen(dmi_uuid) == 1 && dmi_uuid[0] == '\n'  ) {
      continue;
    }
    //如果文本为空，且没有换行符
    if ( strlen(dmi_uuid) == 0 ) {
      continue;
    }
    // 删除可能出现的换行符
    if ( dmi_uuid[strlen(dmi_uuid) - 1] == '\n' ) {
      dmi_uuid[strlen(dmi_uuid) - 1] = '\0';
    }

    return dmi_uuid;
  }

  return NULL;
}

const char* system_get_cpu() {
    static char cpu_model[1024];
    char cpu_tmp[512];

    // try phisical machine
    FILE* model_name = popen("LANG=en lscpu | grep 'Model name:' | cut -d: -f2", "r");
    if(model_name == NULL) {
        printf("Failed to open /proc/cpuinfo\n");
        return NULL;
    }
    if(fgets(cpu_tmp, sizeof(cpu_tmp), model_name) != NULL) {
      if ( cpu_tmp[0] != '-' && cpu_tmp[0] != '\n' && strlen(cpu_tmp) != 0 ) {
        // 先获取一个cpu核心的model name，示例Intel(R) Core(TM) i7-9700 CPU @ 3.00GHz
        // 删除可能出现的换行符
        if ( cpu_tmp[strlen(cpu_tmp) - 1] == '\n' ) {
          cpu_tmp[strlen(cpu_tmp) - 1] = '\0';
        }

        // 获取cpu核心数，并添加到model中
        long cpu_count_long = sysconf(_SC_NPROCESSORS_ONLN);
        char cpu_count_str[10];
        sprintf(cpu_count_str, "%ld", cpu_count_long);
        strcat(cpu_tmp, " x ");
        strcat(cpu_tmp, cpu_count_str);

        // 最终形式为Intel(R) Core(TM) i7-9700 CPU @ 3.00GHz x 8
        memmove(cpu_model, cpu_tmp, strlen(cpu_tmp));
        pclose(model_name);
        printf("%s\n", cpu_model);
        return cpu_model;
      }
    }

    pclose(model_name);


    // try virtual machine
    FILE* vendor_id = popen("LANG=en lscpu | grep 'Vendor ID:' | cut -d: -f2", "r");
    if(vendor_id == NULL) {
        printf("Failed to open /proc/cpuinfo\n");
        return NULL;
    }
    if(fgets(cpu_tmp, sizeof(cpu_tmp), vendor_id) != NULL) {
      if ( cpu_tmp[0] != '-' && cpu_tmp[0] != '\n' && strlen(cpu_tmp) != 0 ) {
        // 先获取一个cpu核心的model name，示例Intel(R) Core(TM) i7-9700 CPU @ 3.00GHz
        // 删除可能出现的换行符
        if ( cpu_tmp[strlen(cpu_tmp) - 1] == '\n' ) {
          cpu_tmp[strlen(cpu_tmp) - 1] = '\0';
        }

        // 最终为apple之类的形式
        memmove(cpu_model, cpu_tmp, strlen(cpu_tmp));
        pclose(vendor_id);
        printf("%s\n", cpu_model);
        return cpu_model;
      }
    }

    pclose(vendor_id);


  perror("Cannot get CPU info\n");
  return NULL;
}

// 首先遍历网络接口列表，然后判断是否是物理网口，并返回第一个物理网口的mac地址
const char* system_get_mac() {
  // 遍历网络接口列表
  // 使用glibc提供的socket和ioctl接口
  int fd;
  struct ifconf ifc;
  char buf[1024];

  if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
    perror("Cannot create socket\n");
    return NULL;
  }

  ifc.ifc_len = sizeof(buf);
  ifc.ifc_buf = buf;
  if (ioctl(fd, SIOCGIFCONF, &ifc) < 0) {
    perror("ioctl(SIOCGIFCONF) error\n");
    close(fd);
    return NULL;
  }

  for (struct ifreq * ifr = ifc.ifc_req; ifr < ifc.ifc_req + (ifc.ifc_len / sizeof(struct ifreq)); ifr++) {
    // IFR->ifr_name即为网络接口名
    char* iface = ifr->ifr_name;
    // 跳过回环
    if (strcmp(iface, "lo") == 0) {
      continue;
    }

    // 判断是否是物理网口
    int is_physical_interface = 0;
    DIR* d;
    char path[512];
    // 对于有线接口，检查"device"目录
    sprintf(path, "/sys/class/net/%s/device", iface);
    d = opendir(path);
    if(d != NULL)
    {
        // 如果设备目录存在，就关闭目录并返回1，说明这是物理接口
        closedir(d);
        is_physical_interface = 1;
    }

    // 对于无线接口，检查"phy80211"目录
    sprintf(path, "/sys/class/net/%s/phy80211", iface);
    d = opendir(path);
    if(d != NULL)
    {
        // 如果无线设备目录存在，就关闭目录并返回1，说明这也是物理接口
        closedir(d);
        is_physical_interface = 1;
    }

    // 非物理网口跳过
    if (is_physical_interface == 0){
      continue;
    }

    // 返回物理网口的mac地址
    // 创建一个 socket
    int fdd;
    struct ifreq ifrd;
    if ((fdd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        perror("socket failed");
        close(fdd);
        return NULL;
    }

    // 选择要查询的网络接口
    strncpy(ifrd.ifr_name, iface, IFNAMSIZ-1);

    if (ioctl(fdd, SIOCGIFHWADDR, &ifrd) < 0) {
        perror("ioctl SIOCGIFHWADDR failed");
        close(fdd);
        return NULL;
    }

    close(fdd);

    // 获取mac地址
    unsigned char mac_address_c[256];
    mac_address_c[0] = (unsigned int)(unsigned char)ifrd.ifr_hwaddr.sa_data[0];
    mac_address_c[1] = (unsigned int)(unsigned char)ifrd.ifr_hwaddr.sa_data[1];
    mac_address_c[2] = (unsigned int)(unsigned char)ifrd.ifr_hwaddr.sa_data[2];
    mac_address_c[3] = (unsigned int)(unsigned char)ifrd.ifr_hwaddr.sa_data[3];
    mac_address_c[4] = (unsigned int)(unsigned char)ifrd.ifr_hwaddr.sa_data[4];
    mac_address_c[5] = (unsigned int)(unsigned char)ifrd.ifr_hwaddr.sa_data[5];

    char mac_address_hex[256];
    sprintf(mac_address_hex, "%02x:%02x:%02x:%02x:%02x:%02x", mac_address_c[0], mac_address_c[1], mac_address_c[2], mac_address_c[3], mac_address_c[4], mac_address_c[5]);

    static char mac_address[256];
    memmove(mac_address, mac_address_hex, strlen(mac_address_hex));

    close(fd);
    return mac_address;
  }

  close(fd);
  return NULL;
}

// 虚拟机条件下，获取根分区文件系统uuid
const char* system_get_blkid() {
    static char uuid[256];
    FILE *fp;
    char line[256];
    char device[64], mount_point[64], fs_type[32];

    fp = fopen("/proc/mounts", "r");
    if (fp == NULL) {
        perror("Failed to open /proc/mounts");
        return NULL;
    }

    while (fgets(line, sizeof(line), fp)) {
        sscanf(line, "%63s %63s %31s", device, mount_point, fs_type);
        if (strcmp(mount_point, "/") == 0) {
            break;
        }
    }

    fclose(fp);

    if (strcmp(mount_point, "/") != 0) {
        fprintf(stderr, "Root filesystem not found\n");
        return NULL;
    }

    char command[128];
    snprintf(command, sizeof(command), "LANG=en blkid %s | sed 's/\"//g'", device);

    fp = popen(command, "r");
    if (fp == NULL) {
        perror("Failed to run blkid command");
        return NULL;
    }

    while (fgets(line, sizeof(line), fp)) {
        char *uuid_pos = strstr(line, " UUID=");
        if (uuid_pos) {
            uuid_pos += 6;  // Move past " UUID="
            char *end = strchr(uuid_pos, ' ');
            if (end) {
                *end = '\0';
            }
            strncpy(uuid, uuid_pos, sizeof(uuid) - 1);
            uuid[sizeof(uuid) - 1] = '\0';  // Ensure null-terminated
            pclose(fp);
            return uuid;
        }
    }

    pclose(fp);
    // todo
    return "unkown disk";
}

// 物理机条件下，获取/boot所在的文件系统挂载点，再获取该挂载点所属物理硬盘接口名，最后获取硬盘wwid
// 如果获取不到wwid，将采用虚拟机方案
const char* system_get_disk_uuid() {
    const char *path = "/boot";
    struct stat statbuf;

    if (stat(path, &statbuf) != 0) {
        perror("stat failed");
        exit(EXIT_FAILURE);
    }

    // Get the device ID (dev_t) from the stat buffer
    dev_t device_id = statbuf.st_dev;

    // Convert the device ID to major and minor numbers
    int major_number = major(device_id);
    int minor_number = minor(device_id);

    struct udev *udev;
    struct udev_device *dev;

    udev = udev_new();

    // 从设备文件(例如/dev/sda1)获取设备
    dev = udev_device_new_from_devnum(udev, 'b', makedev(major_number, minor_number));
    if (!dev) {
        printf("Failed to get device.\n");
        return NULL;
    }

    // 获取这个设备的父设备
    dev = udev_device_get_parent_with_subsystem_devtype(
               dev,
               "block",
               "disk");

    if (!dev) {
        printf("No parent device found.\n");
        return NULL;
    }

    if (!udev_device_get_sysattr_value(dev, "wwid")) {
        fprintf(stderr, "WWID attribute not found, maybe a virtual machine\n");
        udev_unref(udev);
        // 获取不到wwid时，尝试获取blkid
        const char * blkid = system_get_blkid();
        if (blkid) {
          return blkid;
        }
        return NULL;
    }

    udev_unref(udev);

    return udev_device_get_sysattr_value(dev, "wwid");
}

const char* system_get_token() {
    const char *uuid = system_get_uuid();
    const char *os = system_get_name();
    int is_actived = system_is_active();
    long active_timestamp = 0;
    const char *version = system_get_osversion();
    const char *cpu = system_get_cpu();
    const char *mac = system_get_mac();
    const char *disk_uuid = system_get_disk_uuid();

    // 生成token
    unsigned char hash[SHA256_DIGEST_LENGTH];
    static char token[SHA256_DIGEST_LENGTH * 2 + 1];

    SHA256_CTX sha256;
    SHA256_Init(&sha256);
    SHA256_Update(&sha256, uuid, strlen(uuid));
    SHA256_Final(hash, &sha256);
    for(int i = 0; i < SHA256_DIGEST_LENGTH; i++)
    {
        snprintf(token + (i * 2), sizeof(token) - (i * 2), "%02x", hash[i]);
    }

    // 将设备信息发送到服务器
    CURL *curl;
    CURLcode res;

    curl_global_init(CURL_GLOBAL_DEFAULT);

    curl = curl_easy_init();
    if(curl) {
      curl_easy_setopt(curl, CURLOPT_URL, "https://wx.opencloudos.tech/wechatstore");

#ifdef SKIP_PEER_VERIFICATION
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
#endif

#ifdef SKIP_HOSTNAME_VERIFICATION
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
#endif

      // 设置HTTP请求方法为POST
      curl_easy_setopt(curl, CURLOPT_POST, 1L);

      // 启用详细调试
//      curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
//      curl_easy_setopt(curl, CURLOPT_STDERR, stderr);

      // 设置POST数据
      char post_data[4096];
      sprintf(post_data, "{\"token\":\"%s\", \"uuid\":\"%s\", \"os\":\"%s\", \"active\":%d, \"active_timestamp\":%ld, \"version\":\"%s\", \"cpu\":\"%s\", \"mac\":\"%s\", \"disk\":\"%s\"}", token, uuid, os, is_actived, active_timestamp, version, cpu, mac, disk_uuid);
      curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_data);

      // 设置Header
      struct curl_slist *headers = NULL;
      headers = curl_slist_append(headers, "Content-Type: application/json");
      curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);

      // 打印系统环境变量
//      system("env");

      // 执行请求
      res = curl_easy_perform(curl);

      // 检查是否有错误发生
      if(res != CURLE_OK)
        fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));

      // 清除Header列表
      curl_slist_free_all(headers);

      // 完成后，必须释放curl
      curl_easy_cleanup(curl);

      curl_global_cleanup();    
      return token;
    }
    
    curl_global_cleanup();    
    return NULL;
}
